package base;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.Future;

import base.InvertVertexBuilder.InvertVertex;
import collect.Functions;

import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;

public class Support implements Iterable<Entry<Integer,InvertVertex>>{
	
	private final ImmutableMap<Integer,InvertVertex> known;
	
	public static Support fromBuilder(Map<Integer, ImmutableSetMultimap.Builder<Edge, Integer>> builder) {
		ImmutableMap.Builder<Integer,InvertVertex> build = ImmutableMap.builder();
		for (Integer vertex : builder.keySet()) 
			build.put(vertex, InvertVertexBuilder.fromKnown(builder.get(vertex).build()));
		return new Support(build.build());
	}
	
	public static Support fromFutures(Map<Integer, Future<InvertVertex>> builder) {
		return new Support(
			ImmutableMap.<Integer,InvertVertex>builder()
			.putAll(Maps.transformValues(builder, Functions.<InvertVertex>getter()))
			.build()
		);
	}
	
	private Support(ImmutableMap<Integer, InvertVertex> builder) { 
		known = builder;
	}
	
	public int fixedSize() {
		int result = 0;
		for (InvertVertex iv : known.values()) result += iv.g().size() + iv.r().size() + iv.not().size();
		return result;
	}
	
	public int fixedGRSize() {
		int result = 0;
		for (InvertVertex iv : known.values()) result += iv.g().size() + iv.r().size();
		return result;
	}
	
	public InvertVertex fixedInto(Integer vertex) {
		if (vertex < 0 || vertex > known.size()) throw new IllegalArgumentException();
		return known.get(vertex);
	}
	
	public Iterator<Entry<Integer,InvertVertex>> iterator() {
		return known.entrySet().iterator();
	}
	
	public static class NonSelfInhibiting extends Support {
		
		private final static NonSelfInhibiting instance = new NonSelfInhibiting();
		
		public static NonSelfInhibiting get() { return instance; }
		
		private NonSelfInhibiting() {
			super(ImmutableMap.<Integer, InvertVertex>of());
		}
		
		@Override public InvertVertex fixedInto(final Integer vertex) {
			return new InvertVertex.NonSelfInhibit(vertex);
		}
				
	}
	
	public static Builder builder(int forNVertices) {
		return new Builder(forNVertices);
	}
	
	public static FutureBuilder futureBuilder(int forNVertices) {
		return new FutureBuilder(forNVertices);
	}
	
	public static class FutureBuilder {
		public FutureBuilder(int forNVertices) {
			numVertices = forNVertices;
			builder = Maps.newHashMapWithExpectedSize(numVertices);
		}
		
		private final int numVertices;
		private final Map<Integer,Future<InvertVertex>> builder;
		
		public FutureBuilder put(Integer vertex, Future<InvertVertex> result) {
			if(builder.put(vertex, result)!=null) throw new IllegalArgumentException();
			return this;
		}
		
		public Support build() {
			if (builder.size()!=numVertices) throw new IllegalStateException();
			return fromFutures(builder);
		}
		
	}
	
	public static class Builder {
		public Builder(int forNVertices) {
			if (forNVertices <= 0) throw new IllegalArgumentException();
			numVertices = forNVertices;
			builder = Maps.newLinkedHashMap();
			for (Integer vertex=0; vertex < numVertices ; vertex++)
				builder.put(vertex, ImmutableSetMultimap.<Edge,Integer>builder());
			checkVertex = new Predicate<Integer>() {
				@Override public boolean apply(Integer input) {
					return	input == null || input < 0 || input >= numVertices ? false : true;
				}
				
			};
		}

		private final int numVertices;
		private final Map<Integer,ImmutableSetMultimap.Builder<Edge,Integer>> builder;
		private final Predicate<Integer> checkVertex;
		
		public Builder put(Integer to, Edge via, Integer from) {
			if (!checkVertex.apply(to) || !checkVertex.apply(from)) throw new IllegalArgumentException();
			builder.get(to).put(via, from);
			return this;
		}
		
		public Builder put(Integer to, SetMultimap<Edge,Integer> from) {
			if (!checkVertex.apply(to) || from.size() > numVertices) throw new IllegalArgumentException();
			Set<Integer> test = Sets.newHashSetWithExpectedSize(from.size());
			for (Integer i : from.values()) if (!checkVertex.apply(i) || !test.add(i)) throw new IllegalArgumentException();
			builder.get(to).putAll(from);
			return this;
		}
		
		public Support build() {
			return fromBuilder(builder);
		}
	}
	
}
